package com.cc.wechat.service.impl;

import com.cc.wechat.common.ResultCode;
import com.cc.wechat.exception.BusinessException;
import com.cc.wechat.common.constants.MessageConstants;
import com.cc.wechat.config.MinioConfig;
import com.cc.wechat.dto.response.FileUploadResponse;
import com.cc.wechat.service.FileService;
import com.cc.wechat.utils.FileValidationUtils;
import io.minio.*;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 文件服务实现类
 * 基于Minio对象存储实现文件的上传、删除和URL生成
 *
 * @author cc
 * @date 2024/01/07
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class FileServiceImpl implements FileService {

    /**
     * Minio客户端
     */
    private final MinioClient minioClient;

    /**
     * Minio配置
     */
    private final MinioConfig minioConfig;

    /**
     * 上传文件到Minio
     * 1. 生成唯一文件名
     * 2. 上传文件到指定类型目录
     * 3. 生成文件访问URL
     *
     * @param file 文件对象
     * @param type 文件类型（avatar/image等）
     * @return 文件上传响应，包含文件信息和访问URL
     * @throws RuntimeException 上传失败时抛出异常
     */
    @Override
    public FileUploadResponse uploadFile(MultipartFile file, String type) {
        try {
            // 验证文件
            FileValidationUtils.validateFile(file, type);
            
            // 生成文件名
            String extension = FilenameUtils.getExtension(file.getOriginalFilename());
            String objectName = type + "/" + UUID.randomUUID() + "." + extension;

            // 上传文件
            minioClient.putObject(PutObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .build());

            // 生成访问URL
            String url = getFileUrl(objectName);

            return FileUploadResponse.builder()
                    .filename(file.getOriginalFilename())
                    .url(url)
                    .size(file.getSize())
                    .contentType(file.getContentType())
                    .build();
        } catch (BusinessException e) {
            log.error("文件验证失败: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            log.error(MessageConstants.File.UPLOAD_FAILED, e);
            throw new BusinessException(ResultCode.FILE_UPLOAD_FAILED, MessageConstants.File.UPLOAD_FAILED + ": " + e.getMessage());
        }
    }

    /**
     * 从Minio删除文件
     *
     * @param objectName 对象名称（包含路径）
     * @throws RuntimeException 删除失败时抛出异常
     */
    @Override
    public void deleteFile(String objectName) {
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .build());
        } catch (Exception e) {
            log.error(MessageConstants.File.DELETE_FAILED, e);
            throw new BusinessException(ResultCode.FILE_DELETE_FAILED, MessageConstants.File.DELETE_FAILED + ": " + e.getMessage());
        }
    }

    /**
     * 生成文件的预签名URL
     * 生成的URL有效期为7天
     *
     * @param objectName 对象名称（包含路径）
     * @return 可访问的URL地址
     * @throws RuntimeException 生成URL失败时抛出异常
     */
    @Override
    public String getFileUrl(String objectName) {
        try {
            return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()
                    .method(Method.GET)
                    .bucket(minioConfig.getBucketName())
                    .object(objectName)
                    .expiry(7, TimeUnit.DAYS)
                    .build());
        } catch (Exception e) {
            log.error(MessageConstants.File.GET_URL_FAILED, e);
            throw new BusinessException(ResultCode.FILE_URL_GENERATE_FAILED, MessageConstants.File.GET_URL_FAILED + ": " + e.getMessage());
        }
    }
} 